/**
 * @Description:
 * @Author 卞鹏飞 <228443632@qq.com>
 * @create 06/02/25 PM12:49
 */
/* eslint-disable */
import ora from 'ora'
import path from 'node:path'
import fse from 'fs-extra'
import boxen from 'boxen'
import chalk from 'chalk'
import beeper from 'beeper'
import updateNotifier from 'update-notifier'
import pkg from 'zsamc-cli/package.json'
import CliAppEntryContextBase from './app-entry-base'
import shell from 'shelljs'
import {
  BOXEN_OPTS,
  ORA_SPINNER,
  DEFAULT_DIR,
  CLI_NAME,
  APP_TYPE,
  APP_TYPE_REPOSITORY,
  INSTALL_TYPE,
  CACHE_DIR
} from '@/cli/constants.ts'
import { ObjectValueType } from '@/cli/generic-kit.ts'

class AppEntryContext extends CliAppEntryContextBase {
  type: string
  dirName: string
  installType: string
  name: string
  authorName: string
  authorEmail: string
  gitRepository: string
  description: string
  version: string
  private: boolean
  cacheDir = CACHE_DIR

  constructor() {
    super()
    this.type = APP_TYPE.pc
    this.dirName = this._getDefaultDir()
    this.installType = INSTALL_TYPE.pnpm
    this.name = 'zsamc-cli'
    this.authorName = ''
    this.authorEmail = ''
    this.gitRepository = ''
    this.description = ''
    this.version = '1.0.0'
    this.private = false

    this._getDefaultDir = this._getDefaultDir.bind(this)
    this._askForDir = this._askForDir.bind(this)
    this._askDirFlow = this._askDirFlow.bind(this)
    this._askForAppType = this._askForAppType.bind(this)
    this._askForOverwrite = this._askForOverwrite.bind(this)
    this._otherBasicInfoPrompt = this._otherBasicInfoPrompt.bind(this)
  }

  _getDefaultDir(): string {
    return `${this.type}-app`
  }

  _checkVersion(): boolean {
    this.log()
    this.log(`🛠️  检查你的 ${CLI_NAME} 版本...`)

    let checkResult = false
    const notifier = updateNotifier({
      pkg
    })

    const update = notifier.update
    if (update) {
      const messages = [] as string[]
      messages.push(chalk.bgYellow.black(' WARNI: ') + `  ${CLI_NAME} is not latest.\n`)
      messages.push(
        chalk.grey('current ') +
          chalk.grey(update.current) +
          chalk.grey(' → ') +
          chalk.grey('latest ') +
          chalk.green(update.latest)
      )
      messages.push(chalk.grey('Up to date ') + `npm i -g ${pkg.name}`)
      // @ts-expect-error
      this.log(boxen(messages.join('\n'), BOXEN_OPTS))
      beeper()
      this.log(chalk.bgYellow.black(' WARNI: ') + ` 检查你的 ${CLI_NAME}， 警告↑↑, 请尝试升级该版本 → ` + update.latest)
    } else {
      checkResult = true
      this.log(`🛠️ 完成你的检查 ${CLI_NAME}. OK`, chalk.green('✔'))
    }

    return checkResult
  }

  _printEnvInfo() {
    this.log(chalk.grey('当前环境信息:'))
    this.log(chalk.grey(`Node：\t${process.version}`))
    this.log(chalk.grey(`pwd：\t${process.cwd()}`))
  }

  initializing() {
    this.log()

    const version = `(v${pkg.version})`
    const messages = [] as string[]
    messages.push(
      chalk.red(`
███████╗███████╗ █████╗ ███╗   ███╗ ██████╗     ██████╗██╗     ██╗
╚══███╔╝██╔════╝██╔══██╗████╗ ████║██╔════╝    ██╔════╝██║     ██║
  ███╔╝ ███████╗███████║██╔████╔██║██║         ██║     ██║     ██║
 ███╔╝  ╚════██║██╔══██║██║╚██╔╝██║██║         ██║     ██║     ██║
███████╗███████║██║  ██║██║ ╚═╝ ██║╚██████╗    ╚██████╗███████╗██║
╚══════╝╚══════╝╚═╝  ╚═╝╚═╝     ╚═╝ ╚═════╝     ╚═════╝╚══════╝╚═╝
      `)
    )
    messages.push('')
    messages.push(chalk.yellowBright(`欢迎使用 ${CLI_NAME} ${chalk.grey(version)} `))
    messages.push(chalk.yellow('您可以创建一个网页包前端环境.'))
    this.log(
      boxen(messages.join('\n'), {
        ...BOXEN_OPTS,
        ...{
          padding: { top: 1, bottom: 1, right: 10, left: 10 },
          borderColor: 'yellow'
        }
      } as any)
    )

    this._printEnvInfo()
    this._checkVersion()
  }

  /**
   * 选择应用程序类型
   */
  _askForAppType() {
    const opts = [
      {
        type: 'list',
        name: 'type',
        choices: [
          {
            name:
              'cf-manage-template（Rspack，基于 Rust 的高性能 Web 打包工具使用兼容 API 无缝替换 webpack，以个贷中后台前端模版）',
            value: APP_TYPE.cfMange
          },
          {
            name: 'pc（基于vite-cli模板的应用程序，适用于pc、小型和更高的企业级框架）,暂未提供',
            value: APP_TYPE.pc
          },
          {
            name: 'h5（移动应用是基于h5的脚手架，主要用于开发移动web），暂未提供',
            value: APP_TYPE.h5
          }
        ],
        message: '请为您的项目选择应用程序类型：',
        default: APP_TYPE.cfMange
      }
    ]

    return this.prompt(opts).then(({ type }) => {
      this.type = type
      this.dirName = this._getDefaultDir()
    })
  }

  _askForDir(): Promise<void> {
    const opts = [
      {
        type: 'input',
        name: 'dirName',
        message: '请输入项目的目录名：',
        default: this.dirName,
        validate: (dirName: string) => {
          if (dirName.length < 1) {
            beeper()
            return '⚠️  目录名不能为空！'
          }
          return true
        }
      }
    ]

    return this.prompt(opts).then(({ dirName }) => {
      this.dirName = dirName
    })
  }

  async _askForOverwrite(): Promise<void> {
    const destination = this.destinationPath()
    const dirName = this.dirName
    if (!fse.existsSync(path.resolve(destination, dirName))) {
      return Promise.resolve()
    }

    const warn = chalk.grey('小心文件可能被覆盖.')
    const opts = [
      {
        type: 'confirm',
        name: 'overwrite',
        message: `⚠️  目录 ${dirName} 已存在. 是否仍使用此目录? ${warn}`,
        default: false
      }
    ]

    return this.prompt(opts).then(({ overwrite }) => {
      if (!overwrite) {
        this.dirName = DEFAULT_DIR
        return this._askDirFlow()
      }
    })
  }

  _askDirFlow(): Promise<void> {
    return this._askForDir().then(this._askForOverwrite)
  }

  _otherBasicInfoPrompt(): Promise<void> {
    const opts = [
      {
        type: 'input',
        name: 'description',
        message: '项目描述',
        default: 'this is a zsamc-template-web'
      },
      {
        type: 'input',
        name: 'name',
        message: '项目名称',
        default: 'zsamc-cli'
      },
      {
        type: 'input',
        name: 'authorName',
        message: 'author name',
        default: ''
      },
      {
        type: 'input',
        name: 'authorEmail',
        message: 'author email',
        default: ''
      },
      {
        type: 'input',
        name: 'version',
        message: 'project version',
        default: '1.0.0'
      }
    ]
    return this.prompt(opts).then(({ description, authorName, authorEmail, version, name }) => {
      this.name = name
      this.authorEmail = authorEmail
      this.description = description
      this.authorName = authorName
      this.version = version
    })
  }

  async prompting(): Promise<void> {
    return new Promise(resolve => {
      this.log()
      this.log('⚙  基础配置中...')
      this._askForAppType()
        .then(this._askDirFlow)
        .then(this._otherBasicInfoPrompt)
        .then(() => {
          resolve()
        })
    })
  }

  /**
   * 复制文件
   * @param filePath
   * @param templateRoot
   */
  _walk(filePath: string, templateRoot: string) {
    if (fse.statSync(filePath).isDirectory()) {
      fse.readdirSync(filePath).forEach(name => {
        this._walk(path.resolve(filePath, name), templateRoot)
      })
      return
    }

    const relativePath = path.relative(templateRoot, filePath)
    const destination = this.destinationPath(this.dirName, relativePath)

    this.fs.copy(filePath, destination)
  }

  async writing(): Promise<void> {
    return new Promise(async (resolve, reject) => {
      const repository = APP_TYPE_REPOSITORY[this.type]

      this.log('⚙  完成基础配置.', chalk.green('✔'))
      this.log()
      this.log('📂 正在生成项目配置和模板...')

      let spinner = ora({
        text: `正在下载模板中，请耐心等待⌛️ ...\n`,
        spinner: ORA_SPINNER
      }).start()

      const dirAbsolutePath = this.destinationPath(this.dirName)

      fse.ensureDirSync(dirAbsolutePath)

      // if (!fse.existsSync(path.join(dirAbsolutePath, this.cacheDir))) {
      //   fse.ensureDirSync(path.join(dirAbsolutePath, this.cacheDir))
      // }

      const templateRoot = this.destinationPath(this.cacheDir) // git clone到的工程目录
      console.log('git clone', templateRoot)

      shell.exec(`git clone ${repository} ${templateRoot}`, async (code, stdout, stderr) => {
        spinner.stopAndPersist({
          symbol: chalk.green('   ✔'),
          text: `完成下载模板来自 ${repository}`
        })
        spinner = ora({
          text: `正在复制文件到项目中...`,
          spinner: ORA_SPINNER
        }).start()
        console.log('dirAbsolutePath', { dirAbsolutePath, templateRoot })
        this._walk(templateRoot, templateRoot)
        this.fs.commit(async () => {
          spinner.stopAndPersist({
            symbol: chalk.green('   ✔'),
            text: `已完成复制文件到项目中`
          })

          spinner = ora({
            text: `清理tmp文件和文件夹...`,
            spinner: ORA_SPINNER
          }).start()
          fse.removeSync(path.join(dirAbsolutePath, '.git'))
          // fse.removeSync(templateRoot)

          if (fse.statSync(path.join(dirAbsolutePath, 'package.json')).isFile()) {
            const data = fse.readFileSync(path.join(dirAbsolutePath, `package.json`), 'utf8')
            const dataClone = JSON.parse(data)
            dataClone.name = this.name
            dataClone.description = this.description
            dataClone.email = this.authorEmail

            dataClone.author = {
              name: this.authorName || (dataClone.author && dataClone.author.name) || '',
              email: this.authorEmail || (dataClone.author && dataClone.author.email) || '',
              url: this.gitRepository || (dataClone.author && dataClone.author.url) || ''
            }
            dataClone.gitRepository = this.gitRepository
            fse.writeFileSync(path.join(dirAbsolutePath, `package.json`), JSON.stringify(dataClone, null, 2), 'utf8')
          }

          if (fse.existsSync(path.join(dirAbsolutePath, 'public/index.html'))) {
            const data = fse.readFileSync(path.join(dirAbsolutePath, 'public/index.html'), 'utf8')
            let dataClone = data.replace('后台模板标题', this.name)
            fse.writeFileSync(path.join(dirAbsolutePath, 'public/index.html'), dataClone, 'utf8')
          }

          // 清除缓存tmp目录
          fse.removeSync(templateRoot)

          spinner.stopAndPersist({
            symbol: chalk.green('   ✔'),
            text: `完成清除缓存和文件夹的操作`
          })
          resolve()
        })
      })
    })
  }

  async install(): Promise<void> {
    return new Promise(async resolve => {
      this.log()
      this.log('📂 完成项目模板和配置的生成.', chalk.green('✔'))
      this.log()
      const dirAbsolutePath = this.destinationPath(this.dirName)

      const installPkgFunc = (installType: ObjectValueType<typeof INSTALL_TYPE>, resolveCb: () => void) => {
        this.log('📂 安装工程相关包...')
        let spinner = ora({
          text: `waiting...\n`,
          spinner: ORA_SPINNER
        }).start()

        const shString = {
          yarn: 'yarn',
          npm: 'npm i',
          pnpm: 'pnpm i'
        }

        shell.exec(`cd ${dirAbsolutePath} && pwd && ${shString[installType]}`, code => {
          if (code === 0) {
            spinner.stopAndPersist({
              symbol: chalk.green('   ✔'),
              text: `完成安装工程相关包`
            })
            this.log('📂 完成安装工程相关包.', chalk.green('✔'))
          } else {
            // this.log(chalk.yellow(`当前环境不支持${this.installType}安装    X`))
            spinner.stopAndPersist({
              symbol: chalk.redBright(`当前环境不支持${this.installType}安装    X`),
              text: `失败：`
            })
          }
          resolveCb()
        })
      }

      if (fse.existsSync(path.join(dirAbsolutePath, 'package.json'))) {
        if (fse.existsSync(path.join(dirAbsolutePath, 'pnpm-lock.yaml'))) {
          return installPkgFunc('pnpm', resolve)
        }
        if (fse.existsSync(path.join(dirAbsolutePath, 'yarn.lock'))) {
          return installPkgFunc('yarn', resolve)
        }
        return installPkgFunc('npm', resolve)
      } else {
        this.log(chalk.bgYellow.black(' WARNING: ') + ' 📦 未找到Package.json文件，请检查它是否存在 ...')
        resolve()
      }
    })
  }

  end() {
    this.log()
    this.log()
    const dir = chalk.green(this.dirName)
    const info = [] as string[]
    info.push('🎊\t开始使用以下命令')
    info.push(`🎊\t${chalk.yellow(`cd ${this.dirName}`)}`)
    info.push(`🎊\t${chalk.yellow(`${this.installType} run serve`)}`)
    this.log(
      boxen(info.join('\n'), {
        ...BOXEN_OPTS,
        align: 'left',
        borderColor: 'yellow'
      } as any)
    )
    this.log()
    this.log(
      chalk.bgGreenBright.black(' SUCCESS: ') + `\n 📦 创建项目成功！现在你进入${dir}中, 开始编写代码，祝您使用愉快`,
      chalk.green('✔')
    )
  }
}

export default AppEntryContext
